package shard

import (
	"github.com/tietang/go-utils/errs"
	"sort"
	"strconv"
	"sync"
	"sync/atomic"
)

type OverflowType uint32

const (
	New OverflowType = iota
	First
	Last
	PanicError
)

//侦测，用于资源是否可用检测
type Detector interface {
	Detect(shardResource ShardResource) bool //检查该资源是否可用,例如实现ping接口
}

//资源定位
type Router struct {
	Name             string   //router name具有唯一特性
	Detector         Detector //
	Equalize         Equalize
	Plotter          Plotter
	OverflowType     OverflowType
	Resources        []Resource
	shardSize        uint
	DefaultExtraInfo map[string]string //扩展信息
	//	NewShardCallback func() *Shard

	//
	//	shards map[uint]Shard
	//	ids    []uint

	shards []Shard
	apps   map[string]Shard // appName=shard，appName具有唯一型

	//
	counter  uint64
	lock     sync.Mutex
	shardSeq uint32
}

//shardId := uint(atomic.AddUint32(&shard.seq, 1))

//片排序
type ByShardId []Shard

func (p ByShardId) Len() int           { return len(p) }
func (p ByShardId) Less(i, j int) bool { return p[i].ShardId < p[j].ShardId }
func (p ByShardId) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

//新Router
func NewRouter() *Router {
	return new(Router).Init()
}

//初始化Router
func (r *Router) Init() *Router {
	r.DefaultExtraInfo = make(map[string]string)
	r.Resources = make([]Resource, 0, 3)
	//	r.shards = make(map[uint]Shard)
	//	r.ids = make([]uint, 8, 1024)
	r.shards = make([]Shard, 0, 1024)
	r.Equalize = EqualizeByLong
	r.Plotter = new(SimpleLoopPlotter)
	r.OverflowType = PanicError
	r.shardSize = 0
	r.apps = make(map[string]Shard)

	return r
}

//准备Router
func (r *Router) Prepare() {
	sort.Sort(ByShardId(r.shards))
	r.GetShardSize()
	for _, s := range r.shards {
		s.plotter = r.Plotter
	}
}

//添加片到Router
func (r *Router) Add(s *Shard) {
	//	r.shards[s.ShardId] = *s
	//	append(r.ids, s.ShardId)
	s.plotter = r.Plotter
	if s.ShardId == 0 && s.Name != "0" {
		shardId := uint(atomic.AddUint32(&r.shardSeq, 1))
		s.ShardId = shardId
	}
	r.shards = append(r.shards, *s)
	name := s.Name
	r.apps[name] = *s
	//	r.shardSize++
}

//添加片，为某个资源
//func (r *Router) Add(appName string, instanceId string) {

//}

//获取总片数
func (r *Router) GetShardSize() uint {
	if r.shardSize == 0 {
		r.shardSize = uint(len(r.shards))
	}
	return r.shardSize
}

//取任意资源
func (r *Router) LocateAnyByName(name string) (*ShardResource, error) {
	return r.innerLocateByName(name, nil)
}

//通过key查找片
func (r *Router) findShardByName(name string) (*Shard, *uint64, error) {
	if name == "" {
		return nil, nil, errs.NilPointError("name can't be empty")
	}
	var s *Shard
	for _, v := range r.shards {
		if name == v.Name {
			s = &v
			break
		}
	}
	if s == nil {
		return nil, nil, notFoundShardError("Not found shard by shard name:" + name)
	}
	seed := atomic.AddUint64(&r.counter, 1)
	return s, &seed, nil
}

//取任意资源
func (r *Router) LocateAny(key Key) (*ShardResource, error) {
	return r.innerLocate(key, nil)
}

//通过key查找片
func (r *Router) findShardByKey(key Key) (*Shard, *uint64, error) {
	if key == nil {
		return nil, nil, errs.NilPointError("key can't be nil")
	}
	id, _ := r.Equalize(key, r.GetShardSize())
	if id < 0 {
		return nil, nil, keyGreaterThanZeroError("id=" + strconv.Itoa(int(id)))
	}
	s := r.findShardById(id)
	if s == nil {
		return nil, nil, notFoundShardError("Not found shard by shardId:" + strconv.Itoa(int(id)))
	}
	seed := atomic.AddUint64(&r.counter, 1)
	return s, &seed, nil
}

//通过Key值来查找片
func (r *Router) findShardById(id uint) *Shard {
	var s *Shard
	prev := r.shards[0]
	for _, v := range r.shards {
		if (id > prev.ShardId && id <= v.ShardId) || id == v.ShardId {
			s = &v
			break
		}
	}
	switch r.OverflowType {
	case New:
	case First:
		s = &(r.shards[0])
	case Last:
		s = &(r.shards[len(r.shards)-1])
	case PanicError:
	default:

	}
	return s
}

//取指定ResourceFeaturen资源
func (r *Router) Locate(key Key, resourceFeature ResourceFeature) (*ShardResource, error) {
	return r.innerLocate(key, &resourceFeature)
}

//取指定ResourceFeature资源
func (r *Router) innerLocate(key Key, resourceFeature *ResourceFeature) (*ShardResource, error) {
	s, seed, err := r.findShardByKey(key)
	if err != nil {
		return nil, err
	}
	return r.findShardResource(s, seed, resourceFeature)
}

//取指定ResourceFeature资源
func (r *Router) innerLocateByName(name string, resourceFeature *ResourceFeature) (*ShardResource, error) {
	s, seed, err := r.findShardByName(name)
	if err != nil {
		return nil, err
	}
	return r.findShardResource(s, seed, resourceFeature)
}

//取指定ResourceFeature资源
func (r *Router) findShardResource(s *Shard, seed *uint64, resourceFeature *ResourceFeature) (*ShardResource, error) {
	var sr *ShardResource
	var err0 error
	if resourceFeature == nil {
		sr, err0 = s.GetAny(uint(*seed))
	} else {
		sr, err0 = s.Get(uint(*seed), *resourceFeature)
	}
	if err0 != nil {
		return nil, notFoundShardResourceError("Not found shard resource by shardId: " + strconv.Itoa(int(s.ShardId)))
	}
	return sr, nil
}

//通过key值和特定ResourceFeature查找片资源
func (r *Router) findByExistedId(id uint, resourceFeature *ResourceFeature) (*ShardResource, error) {
	s := r.findShardById(id)
	if s == nil {
		return nil, notFoundShardError("This error should not be happened.")
	}
	seed := atomic.AddUint64(&r.counter, 1)
	return r.findShardResource(s, &seed, nil)
}

//取第一个片任意资源
func (r *Router) FirstAny(key Key) (*ShardResource, error) {
	if len(r.shards) <= 0 {
		return nil, notFoundShardError("Not found any shard.")
	}
	id := r.shards[0].ShardId
	return r.findByExistedId(id, nil)
}

//取第一个片指定ResourceType资源
func (r *Router) First(key Key, resourceFeature ResourceFeature) (*ShardResource, error) {
	if len(r.shards) <= 0 {
		return nil, notFoundShardError("Not found any shard.")
	}
	id := r.shards[0].ShardId
	return r.findByExistedId(id, &resourceFeature)
}

//取最后一个片任意资源
func (r *Router) LastAny(key Key) (*ShardResource, error) {
	size := len(r.shards)
	if size <= 0 {
		return nil, notFoundShardError("Not found any shard.")
	}
	id := r.shards[size-1].ShardId
	return r.findByExistedId(id, nil)
}

//取最后一个片指定ResourceType资源
func (r *Router) Last(key Key, resourceFeature ResourceFeature) (*ShardResource, error) {
	size := len(r.shards)
	if size <= 0 {
		return nil, notFoundShardError("Not found any shard.")
	}
	id := r.shards[size-1].ShardId
	return r.findByExistedId(id, &resourceFeature)
}
